package gsw.demo.pluggable.plugin_simple;

import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Message;
import android.view.View;

import java.io.File;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;
import gsw.tool.pluggable.core.Env;

/**
 * 插件化需要实现两件事：1.免注册；2.加载插件类
 * 1.免注册.Activity的启动分为3步：startActivity、PMS检查、luanchAtivity.Hook时，只能拦截startActivity和luanchAtivity.
 * 2.加载插件类.有两种方式:dex融合和loadedApk插入
 * 注意：暂未处理兼容性问题，所以建议所有的Activity都使用android.app.Activity
 */
@SuppressLint("PrivateApi")
public class HookManager {
    private static final HookManager ourInstance = new HookManager();

    private Context context;
    public static String SPLOGINSTATE = "SPLoginState";
    public static String SPLOGIN = "SPLogin";
    //加载类的模式：0代表dex融合；1代表loadedApk插入
    int mode = 0;
    //保存插件的loadedApk对象，防止ActivityThread的mPackages中弱引用的loadedAPK对象被回收
    private Map<String, Object> loadedApks;

    private HookManager() {
        loadedApks = new HashMap<>();
    }

    public static HookManager getInstance() {
        return ourInstance;
    }

    /**
     * 拦截startActivity
     * 步骤如下：
     * 1.通过反射获取系统的ActivityManagerNative的gDefault属性；
     * 2.通过反射gDefault，获取IactivityManager属性
     * 3.使用动态代理的IactivityManager对象，替换gDefault的IactivityManager属性
     *
     * @param context 上下文
     */
    public void hookStartActivity(Context context) {
        this.context = context;
        try {
            /**
             * 通过反射获取系统的ActivityManagerNative的gDefault属性
             * 注意：该方法在API26以上已废弃
             */
            Class<?> ActivityManagerNativecls = Class.forName("android.app.ActivityManagerNative");
            Field gDefault = ActivityManagerNativecls.getDeclaredField("gDefault");
            gDefault.setAccessible(true);
            //得到ActivityManagerNative的gDefault属性
            Object defaltValue = gDefault.get(null);

            //mInstance对象
            Class<?> SingletonClass = Class.forName("android.util.Singleton");
            Field mInstance = SingletonClass.getDeclaredField("mInstance");
            //还原 IactivityManager对象  系统对象
            mInstance.setAccessible(true);
            Object iActivityManagerObject = mInstance.get(defaltValue);

            Class<?> IActivityManagerIntercept = Class.forName("android.app.IActivityManager");
            ActivtyStart startActivtyMethod = new ActivtyStart(iActivityManagerObject);
            //动态代理iActivityManagerObject，对其进行扩展，增加意图替换的逻辑
            Object oldIactivityManager = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader()
                    , new Class[]{IActivityManagerIntercept, View.OnClickListener.class}
                    , startActivtyMethod);

            //使用动态代理的IactivityManager对象，替换gDefault的IactivityManager属性
            mInstance.set(defaltValue, oldIactivityManager);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 拦截luanchAtivity
     * 通过替换Hnadler实现
     *
     * @param context
     */
    public void hookHookMh(Context context) {
        try {
            Class<?> forName = Class.forName("android.app.ActivityThread");
            Field currentActivityThreadField = forName.getDeclaredField("sCurrentActivityThread");
            currentActivityThreadField.setAccessible(true);
            //获取系统的ActivityTread的sCurrentActivityThread属性
            Object activityThreadObj = currentActivityThreadField.get(null);

            Field handlerField = forName.getDeclaredField("mH");
            handlerField.setAccessible(true);
            //获取sCurrentActivityThread的mH对象
            Handler mH = (Handler) handlerField.get(activityThreadObj);

            Field callbackField = Handler.class.getDeclaredField("mCallback");
            callbackField.setAccessible(true);
            //使用自定义的mCallback对象替换mH的mCallback属性
            callbackField.set(mH, new ActivityMH(mH));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 加载插件类之dex融合
     * 将插件APK的dex文件加入宿主APK中，即将插件APK中的类加入宿主APK中
     *
     * @param apkPath 插件的原始路径
     */
    public void injectPluginClass(String apkPath) {
        //设置加载类的模式：0代表dex融合
        mode = 0;

        //插件的缓存路径
        String cachePath = context.getCacheDir().getAbsolutePath();
        //插件的DexClassLoader对象
        DexClassLoader dexClassLoader = new DexClassLoader(apkPath, cachePath, cachePath, context.getClassLoader());
        try {
            /**
             * 第一步，将插件APK添加到宿主APk中，然后得到插件的dex数组:
             * 先获取插件的BaseDexClassLoader打的pathList对象，再获取pathList对象的dexElements对象，即为插件的dex数组
             */
            //获取插件的BaseDexClassLoader打的pathList对象
            Class myDexClazzLoader = Class.forName("dalvik.system.BaseDexClassLoader");
            Field myPathListFiled = myDexClazzLoader.getDeclaredField("pathList");
            myPathListFiled.setAccessible(true);
            Object myPathListObject = myPathListFiled.get(dexClassLoader);

            //获取pathList对象的dexElements对象
            Class myPathClazz = myPathListObject.getClass();
            Field myElementsField = myPathClazz.getDeclaredField("dexElements");
            myElementsField.setAccessible(true);
            //插件的dex数组
            Object myElements = myElementsField.get(myPathListObject);

            /**
             * 第二步，获取宿主的dex数组
             */
            //获取宿主的BaseDexClassLoader打的pathList对象
            PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();
            Class baseDexClazzLoader = Class.forName("dalvik.system.BaseDexClassLoader");
            Field pathListFiled = baseDexClazzLoader.getDeclaredField("pathList");
            pathListFiled.setAccessible(true);
            Object pathListObject = pathListFiled.get(pathClassLoader);

            //获取pathList对象的dexElements对象
            Class systemPathClazz = pathListObject.getClass();
            Field systemElementsField = systemPathClazz.getDeclaredField("dexElements");
            systemElementsField.setAccessible(true);
            //宿主的dex数组
            Object systemElements = systemElementsField.get(pathListObject);

            /**
             * 第三步，将插件的dex数组插入宿主的dex数组中，即将插件的类添加到宿主中
             */
            int systemLength = Array.getLength(systemElements);
            int myLength = Array.getLength(myElements);
            //获取宿主的dex数组的类型
            Class<?> sigleElementClazz = systemElements.getClass().getComponentType();
            int newSysteLength = myLength + systemLength;
            //根据宿主的dex数组的类型，新建一个dex数组，新数组的长度为插件dex数组和宿主dex数组的长度之和
            Object newElementsArray = Array.newInstance(sigleElementClazz, newSysteLength);
            //将插件dex数组和宿主dex数组中的添加到新的dex数组中
            for (int i = 0; i < newSysteLength; i++) {
                if (i < myLength) {
                    Array.set(newElementsArray, i, Array.get(myElements, i));
                } else {
                    Array.set(newElementsArray, i, Array.get(systemElements, i - myLength));
                }
            }
            Field elementsField = pathListObject.getClass().getDeclaredField("dexElements");
            elementsField.setAccessible(true);
            //使用新的dex数组替换原来的宿主dex数组
            elementsField.set(pathListObject, newElementsArray);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 加载插件类之loadedApk插入
     *
     * @param path
     */
    public void putLoadedApk(String path) {
        //设置加载类的模式：1代表loadedApk插入
        mode = 1;

        try {
            /**
             * 获取系统的ActivityThread对象
             */
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
            currentActivityThreadMethod.setAccessible(true);
            Object currentActivityThread = currentActivityThreadMethod.invoke(null);

            /**
             * 获取ActivityThread的mPackages对象
             * ActivityThread的mPackages对象是用来保存loadedApk对象，加载插件类就是把插件的loadedApk对象插到mPackages中
             */
            Field mPackagesField = activityThreadClass.getDeclaredField("mPackages");
            mPackagesField.setAccessible(true);
            //ActivityThread的mPackages对象
            Map mPackages = (Map) mPackagesField.get(currentActivityThread);

            /**
             * 生成插件的loadedApk对象
             * 使用ActivityThread的getPackageInfoNoCheck方法生成loadedApk对象，需要两个参数ApplicationInfo和CompatibilityInfo
             */
            Class<?> compatibilityInfoClass = Class.forName("android.content.res.CompatibilityInfo");
            //得到getPackageInfoNoCheck方法
            Method getPackageInfoNoCheckMethod = activityThreadClass.getDeclaredMethod("getPackageInfoNoCheck", ApplicationInfo.class, compatibilityInfoClass);
            Field defaultCompatibilityInfoField = compatibilityInfoClass.getDeclaredField("DEFAULT_COMPATIBILITY_INFO");
            //生成默认的CompatibilityInfo对象
            Object defaultCompatibilityInfo = defaultCompatibilityInfoField.get(null);
            //生成插件的ApplicationInfo对象
            ApplicationInfo applicationInfo = getApplicationInfo(context, path);
            //生成插件的loadedApk对象
            Object loadedApk = getPackageInfoNoCheckMethod.invoke(currentActivityThread, applicationInfo, defaultCompatibilityInfo);

            /**
             * 设置插件的loadedApk的mClassLoader对象
             */
            //插件的缓存路径
            String odexPath = HookTool.getPluginOptDexDir(applicationInfo.packageName).getPath();
            //插件的lib路径
            String libDir = HookTool.getPluginLibDir(applicationInfo.packageName).getPath();
            //插件的ClassLoader对象
            ClassLoader classLoader = new MyClassLoader(path, odexPath, libDir, context.getClassLoader());
            Field mClassLoaderField = loadedApk.getClass().getDeclaredField("mClassLoader");
            mClassLoaderField.setAccessible(true);
            //设置插件的loadedApk的mClassLoader对象
            mClassLoaderField.set(loadedApk, classLoader);

            /**
             * 把插件的loadedApk对象插入ActivityThread的mPackages中
             */
            WeakReference weakReference = new WeakReference(loadedApk);
            mPackages.put(applicationInfo.packageName, weakReference);
            loadedApks.put(applicationInfo.packageName, loadedApk);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 生成插件的ApplicationInfo对象
     *
     * @param context 上下文
     * @param path    插件的路径
     * @return 插件的ApplicationInfo对象
     */
    private ApplicationInfo getApplicationInfo(Context context, String path) {
        try {
            /**
             * 生成PackageParser对象
             */
            Class packageParserClass = Class.forName("android.content.pm.PackageParser");
            Object packageParser = packageParserClass.newInstance();

            /**
             * 生成Package对象,即清单文件中注册的组件
             * parsePackage方法有3个参数：
             * 第一个参数代表PackageParser对象
             * 第二个参数代表APK文件
             * 第三个参数代表标志位
             */
            Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage", File.class, int.class);
            Object packageObj = parsePackageMethod.invoke(packageParser, new File(path), PackageManager.GET_ACTIVITIES);

            /**
             * 生成PackageUserState对象
             */
            Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");
            Object defaltUserState = packageUserStateClass.newInstance();

            /**
             * 生成ApplicationInfo对象
             */
            Method generateApplicationInfoMethod = packageParserClass.getDeclaredMethod("generateApplicationInfo",
                    packageObj.getClass(), int.class, packageUserStateClass);
            ApplicationInfo applicationInfo = (ApplicationInfo) generateApplicationInfoMethod.invoke(packageParser, packageObj, 0, defaltUserState);

            /**
             *设置ApplicationInfo的代码路径
             */
            applicationInfo.sourceDir = path;
            applicationInfo.publicSourceDir = path;
            return applicationInfo;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 拦截IPackageManage.getPackageInfo()
     * 系统调用handleLuachActivity()时，会通过IPackageManage.getPackageInfo()检查Activity的包名。
     * 如果IPackageManage.getPackageInfo()返回的包名为null，则使用activityInfo.applicationInfo.packageName为Activitry的包名；
     * 如果IPackageManage.getPackageInfo()返回的包名不为null，则与activityInfo.applicationInfo.packageName比较，如果不同，就会报错。
     * <p>
     * 现在我们需要设置Activity的包名为插件的包名，就需要拦截IPackageManage.getPackageInfo()，让IPackageManage.getPackageInfo()返回的包名为null。
     */
    public void hookPackgeManager() {
        try {
            /**
             * 获取系统的ActivityThread对象
             */
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
            currentActivityThreadMethod.setAccessible(true);
            Object currentActivityThread = currentActivityThreadMethod.invoke(null);

            /**
             * 获取ActivityThread的sPackageManager对象
             */
            Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
            sPackageManagerField.setAccessible(true);
            Object sPackageManager = sPackageManagerField.get(currentActivityThread);

            /**
             * 使用代理的IPackageManager对象，替换ActivityThread的sPackageManager对象
             */
            Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
            Object proxy = Proxy.newProxyInstance(iPackageManagerInterface.getClassLoader()
                    , new Class[]{iPackageManagerInterface}, new IPackageManagerHandler(sPackageManager));
            sPackageManagerField.set(currentActivityThread, proxy);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 动态代理系统的IActivityManager
     * 使用ActivityProxy代替真实的跳转目标，实现清单文件免注册
     */
    class ActivtyStart implements InvocationHandler {
        private Object iActivityManagerObject;

        public ActivtyStart(Object iActivityManagerObject) {
            this.iActivityManagerObject = iActivityManagerObject;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //动态代理startActivity方法
            if ("startActivity".equals(method.getName())) {
                Intent intent = null;
                int index = 0;
                //遍历startActivity的参数，找到Intent类型的参数
                for (int i = 0; i < args.length; i++) {
                    Object arg = args[i];
                    if (arg instanceof Intent) {
                        intent = (Intent) args[i];
                        index = i;
                    }
                }
                Intent newIntent = new Intent();
                //修改跳转目标为ActivityProxy，用于实现清单文件免注册
                ComponentName componentName = new ComponentName(context, ActivityProxy.class);
                newIntent.setComponent(componentName);
                //保存真实的意图
                newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent);
                args[index] = newIntent;
            }

            return method.invoke(iActivityManagerObject, args);
        }
    }

    /**
     * 自定义Callback，用于替换系统的Callback
     */
    class ActivityMH implements Handler.Callback {
        private Handler mH;

        public ActivityMH(Handler mH) {
            this.mH = mH;
        }

        @Override
        public boolean handleMessage(Message msg) {
            //msg.what==100，代表加载activity
            if (msg.what == 100) {
                //根据登录状态设置跳转目标
                handleLuachActivity(msg);
            }
            //调用系统的handleMessage(),执行luanchActivity
            mH.handleMessage(msg);
            return true;
        }

        /**
         * 根据登录状态设置跳转目标
         *
         * @param msg
         */
        private void handleLuachActivity(Message msg) {
            Object obj = msg.obj;
            try {
                Field intentField = obj.getClass().getDeclaredField("intent");
                intentField.setAccessible(true);
                //代理意图
                Intent proxyIntent = (Intent) intentField.get(obj);
                //真实意图
                Intent oldIntent = proxyIntent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
                if (oldIntent != null) {
                    //集中式登录
                    SharedPreferences share = context.getSharedPreferences(SPLOGIN, Context.MODE_PRIVATE);
                    if (share.getBoolean(SPLOGINSTATE, false)) {
                        //还原真实的意图
                        proxyIntent.setComponent(oldIntent.getComponent());
                    } else {
                        //修改跳转目标为ActivityLogin
                        ComponentName componentName = new ComponentName(context, ActivityLogin.class);
                        proxyIntent.putExtra(Env.EXTRA_TARGET_INTENT, oldIntent);
                        proxyIntent.setComponent(componentName);
                    }

                    //如果是loadedApk插入的模式，就需要修改插件APK的组件的applicationInfo.packageName
                    if (mode == 1) {
                        /**
                         * 获取activityInfo对象
                         */
                        Field activityInfoField = obj.getClass().getDeclaredField("activityInfo");
                        activityInfoField.setAccessible(true);
                        ActivityInfo activityInfo = (ActivityInfo) activityInfoField.get(obj);
                        /**
                         * 修改activityInfo的包名。如果是宿主的Activity，则不需要修改包名；如果是插件的Activity，就需要修改为插件的包名。
                         * 因为使用loadedApk插入的方式加载插件的类时，会生成新的loadedApk对象，这个时候就需要根据插件的包名，从ActivityThrea中查找插件的loadedApk对象。
                         */
                        activityInfo.applicationInfo.packageName = proxyIntent.getComponent().getPackageName();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 自定义DexClassLoader
     */
    public static class MyClassLoader extends DexClassLoader {
        public MyClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) {
            super(dexPath, optimizedDirectory, libraryPath, parent);
        }
    }

    /**
     * 自定义InvocationHandler
     * 用来动态代理IPackageManager.getPackgeInfo()，让IPackageManage.getPackageInfo()返回的包名为null，从而使用插件的包名作为Activity的包名
     */
    public class IPackageManagerHandler implements InvocationHandler {
        private Object mBase;

        public IPackageManagerHandler(Object mBase) {
            this.mBase = mBase;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getName().equals("getPackageInfo")) {
                return new PackageInfo();
            }
            return method.invoke(mBase, args);
        }
    }
}
